using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Headsight.BPMN2WF.BPMN.Model;
using Headsight.BPMN2WF.BPMN.Schemas;
using Headsight.BPMN2WF.Testing.TestObjCtors;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Headsight.BPMN2WF.BPMN.BlockModel
{
    [TestClass]
    public abstract class BPMNProcessToBlocksTestFixture<T> where T : IBPMNFlowNodesBlock2BlockTransformer
    {
        protected BPMNFlowNodesBlock _bpmnProcess;
        protected T _sut;

        [TestInitialize]
        public void SetUpFixture()
        {
            _sut = CreateSut();
        }

        protected abstract T CreateSut();

        protected void CreateBPMNProcess(tProcessCtor tProcessCtor)
        {
            var tProcess = tProcessCtor.Create();
            var tDefinitions = new tDefinitionsCtor().With(tProcess).Create();

            _bpmnProcess = new Model.BPMNFlowNodesBlockFactory().CreateBPMNFlowNodesBlock(tDefinitions);
        }

        private static void Verify(BPMNFlowNodesBlock actual, string expectedNodeId, BPMNFlowNode actualNode)
        {
            Assert.AreSame(actual.GetBPMNFlowNodeById(expectedNodeId), actualNode, String.Format("actual: {0}", actualNode));
            Assert.IsInstanceOfType(actualNode, typeof(BPMNFlowNode));
        }

        private static void VerifyBlock(BPMNFlowNodesBlock actual, string expectedNodeId, BPMNFlowNode actualNode)
        {
            Assert.AreSame(actual.GetBPMNFlowNodeById(expectedNodeId), actualNode, String.Format("Expected: {0} /// Actual: {1}", actual.GetBPMNFlowNodeById(expectedNodeId), actualNode));
            Assert.IsInstanceOfType(actualNode, typeof(BPMNFlowNodesBlock));
        }

        private static void IsFlowNode(BPMNFlowNode actual)
        {
            Assert.IsNotInstanceOfType(actual, typeof(BPMNFlowNodesBlock));
        }

        private static void IsBlock(BPMNFlowNode actual)
        {
            Assert.IsInstanceOfType(actual, typeof(BPMNFlowNodesBlock));
        }

        [TestMethod]
        public void CreateBlock_inco_sequential_flow()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("_id1")
                .Flow("_id1", "_id2")
                .Flow("_id2", "_id3")
                .Flow("_id3", "_id4"));

            var actual = _sut.Transform(_bpmnProcess);

            var nodeId1 = actual.FlowNodeIn;
            var nodeId2 = actual.GetNextNodesInFlowFrom(nodeId1)[0];
            var nodeId3 = actual.GetNextNodesInFlowFrom(nodeId2)[0];
            var nodeId4 = actual.GetNextNodesInFlowFrom(nodeId3)[0];

            Verify(actual, "_id1", nodeId1);
            Verify(actual, "_id2", nodeId2);
            Verify(actual, "_id3", nodeId3);
            Verify(actual, "_id4", nodeId4);
            Assert.AreEqual(null, actual.NodeOut);
        }

        [TestMethod]
        public void CreateBlock_inco_diverging_into_2_branches_both_converging_again()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("_id1")
                .Flow("_id1", "_id2")
                .Flow("_id2", "_id_a_branch")
                .Flow("_id2", "_id_b_branch")
                .Flow("_id_a_branch", "_id_end")
                .Flow("_id_b_branch", "_id_end"));

            var actual = _sut.Transform(_bpmnProcess);

            //
            // 1 Node + 1 Block
            //
            var nodes = actual.AllNodes;
            Assert.AreEqual(2, nodes.Count);
            // Vertex
            IsFlowNode(actual.FlowNodeIn);
            Verify(actual, "_id1", actual.FlowNodeIn);
            // Block
            var nodesFromNodeIn = actual.GetNextNodesInFlowFrom(actual.FlowNodeIn);
            Assert.AreEqual(1, nodesFromNodeIn.Count);
            IsBlock(nodesFromNodeIn[0]);
            Verify(actual, "block__id2", nodesFromNodeIn[0]);
            Assert.AreEqual(0, actual.GetNextNodesInFlowFrom(nodesFromNodeIn[0]).Count);

            //
            // Previous block: 1 Vertex + 2 x block + 1 vertext
            //
            var block__id2 = (BPMNFlowNodesBlock)nodesFromNodeIn[0];
            var block__id2_allNodes = block__id2.AllNodes;
            Assert.AreEqual(4, block__id2_allNodes.Count);

            IsFlowNode(block__id2.FlowNodeIn);
            Verify(actual, "_id2", block__id2.FlowNodeIn);

            // 2 blocks
            var block__id_a_branch = (BPMNFlowNodesBlock)block__id2_allNodes.First(x => x.Id == "block__id_a_branch");
            var block__id_b_branch = (BPMNFlowNodesBlock)block__id2_allNodes.First(x => x.Id == "block__id_b_branch");

            // _id2 points to blockId2_01/02
            CollectionAssert.AreEquivalent(new[] {block__id_a_branch, block__id_b_branch}, (ICollection) block__id2.GetNextNodesInFlowFrom(block__id2.FlowNodeIn));
            // both previous block point to _id_end
            Assert.AreEqual(1, block__id2.GetNextNodesInFlowFrom(block__id_a_branch).Count);
            Assert.AreEqual(1, block__id2.GetNextNodesInFlowFrom(block__id_b_branch).Count);
            Assert.AreEqual(true, block__id2.GetNextNodesInFlowFrom(block__id_a_branch).Any(x => x.Id == "_id_end"));
            Assert.AreEqual(true, block__id2.GetNextNodesInFlowFrom(block__id_b_branch).Any(x => x.Id == "_id_end"));

            Assert.AreEqual(null, block__id2.NodeOut);

            // _id_a_branch_block // single vertex
            Assert.AreEqual(1, block__id_a_branch.AllNodes.Count);
            Verify(actual, "_id_a_branch", block__id_a_branch.FlowNodeIn);
            Assert.AreSame(block__id_a_branch.FlowNodeIn, block__id_a_branch.NodeOut);

            // _id_b_branch_block // single vertex
            Assert.AreEqual(1, block__id_b_branch.AllNodes.Count);
            Verify(actual, "_id_b_branch", block__id_b_branch.FlowNodeIn);
            Assert.AreSame(block__id_b_branch.FlowNodeIn, block__id_b_branch.NodeOut);
        }

        [TestMethod]
        public void CreateBlock_inco_diverging_into_2_branches_both_having_sequential_flows_and_finally_converging_again()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("_id1")
                .Flow("_id1", "_id2")
                .Flow("_id2", "_id_a_branch")
                .Flow("_id2", "_id_b_branch")
                .Flow("_id_a_branch", "_id_a_branch_elem1")
                .Flow("_id_b_branch", "_id_b_branch_elem1")
                .Flow("_id_a_branch_elem1", "_id_end")
                .Flow("_id_b_branch_elem1", "_id_end"));

            var actual = _sut.Transform(_bpmnProcess);

            //
            // 1 Node + 1 Block
            //
            var nodes = actual.AllNodes;
            Assert.AreEqual(2, nodes.Count);
            // Vertex
            IsFlowNode(actual.FlowNodeIn);
            Verify(actual, "_id1", actual.FlowNodeIn);
            // Block
            var nodesFromNodeIn = actual.GetNextNodesInFlowFrom(actual.FlowNodeIn);
            Assert.AreEqual(1, nodesFromNodeIn.Count);
            IsBlock(nodesFromNodeIn[0]);
            Verify(actual, "block__id2", nodesFromNodeIn[0]);
            Assert.AreEqual(0, actual.GetNextNodesInFlowFrom(nodesFromNodeIn[0]).Count);

            //
            // Previous block: 1 Vertex + 2 x block + 1 vertext
            //
            var block__id2 = (BPMNFlowNodesBlock)nodesFromNodeIn[0];
            var block__id2_allNodes = block__id2.AllNodes;
            Assert.AreEqual(4, block__id2_allNodes.Count);

            IsFlowNode(block__id2.FlowNodeIn);
            Verify(actual, "_id2", block__id2.FlowNodeIn);

            // 2 blocks
            var block__id_a_branch = (BPMNFlowNodesBlock)block__id2_allNodes.First(x => x.Id == "block__id_a_branch");
            var block__id_b_branch = (BPMNFlowNodesBlock)block__id2_allNodes.First(x => x.Id == "block__id_b_branch");

            // _id2 points to blockId2_01/02
            CollectionAssert.AreEquivalent(new[] { block__id_a_branch, block__id_b_branch }, (ICollection)block__id2.GetNextNodesInFlowFrom(block__id2.FlowNodeIn));
            // both previous block point to _id_end
            Assert.AreEqual(1, block__id2.GetNextNodesInFlowFrom(block__id_a_branch).Count);
            Assert.AreEqual(1, block__id2.GetNextNodesInFlowFrom(block__id_b_branch).Count);
            Assert.AreEqual(true, block__id2.GetNextNodesInFlowFrom(block__id_a_branch).Any(x => x.Id == "_id_end"));
            Assert.AreEqual(true, block__id2.GetNextNodesInFlowFrom(block__id_b_branch).Any(x => x.Id == "_id_end"));

            Assert.AreEqual(null, block__id2.NodeOut);

            // _id_a_branch_block // single vertex
            Assert.AreEqual(2, block__id_a_branch.AllNodes.Count);
            Verify(actual, "_id_a_branch", block__id_a_branch.FlowNodeIn);
            Verify(actual, "_id_a_branch_elem1", block__id_a_branch.GetNextNodesInFlowFrom(block__id_a_branch.FlowNodeIn)[0]);
            Assert.AreSame(actual.GetBPMNFlowNodeById("_id_a_branch_elem1"), block__id_a_branch.NodeOut);

            // _id_b_branch_block // single vertex
            Assert.AreEqual(2, block__id_b_branch.AllNodes.Count);
            Verify(actual, "_id_b_branch", block__id_b_branch.FlowNodeIn);
            Verify(actual, "_id_b_branch_elem1", block__id_b_branch.GetNextNodesInFlowFrom(block__id_b_branch.FlowNodeIn)[0]);
            Assert.AreSame(actual.GetBPMNFlowNodeById("_id_b_branch_elem1"), block__id_b_branch.NodeOut);
        }

        [TestMethod]
        public void CreateBlock_() {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("start")                
                .Flow("start", "elem_start")
                .Flow("elem_start", "branch_a")
                .Flow("elem_start", "branch_b")
                .Flow("branch_a", "elem_end")
                .Flow("branch_b", "elem_end")
                .Flow("elem_end", "end"));

            var actual = _sut.Transform(_bpmnProcess);

            //
            // 1 Vertex + 1 Block + 1 Vertext
            //
            Assert.AreEqual(3, actual.AllNodes.Count);
            // Vertex
            IsFlowNode(actual.FlowNodeIn);
            Verify(actual, "start", actual.FlowNodeIn);

            // Block
            var actual_nodeIn_nextNodes = actual.GetNextNodesInFlowFrom(actual.FlowNodeIn);
            Assert.AreEqual(1, actual_nodeIn_nextNodes.Count);
            var block_elem_start = (BPMNFlowNodesBlock)actual_nodeIn_nextNodes[0];
            Verify(actual, "block_elem_start", block_elem_start);
            Verify(actual, "elem_start", block_elem_start.FlowNodeIn);

            var block_elem_start_next_nodes = block_elem_start.GetNextNodesInFlowFrom(block_elem_start.FlowNodeIn);
            Assert.AreEqual(2, block_elem_start_next_nodes.Count);
            var elem_end1 = block_elem_start.GetNextNodesInFlowFrom(block_elem_start_next_nodes[0])[0];
            var elem_end2 = block_elem_start.GetNextNodesInFlowFrom(block_elem_start_next_nodes[1])[0];
            
            Assert.AreEqual(elem_end1, block_elem_start.NodeOut);

            Assert.AreEqual(elem_end1, elem_end2);
            Assert.AreEqual("elem_end", elem_end1.Id);
            var block_branch_a = (BPMNFlowNodesBlock)block_elem_start_next_nodes[0];
            var block_branch_b = (BPMNFlowNodesBlock)block_elem_start_next_nodes[1];
            Assert.AreEqual("block_branch_a", block_branch_a.Id);
            Assert.AreEqual("block_branch_b", block_branch_b.Id);
            Assert.AreEqual("branch_a", block_branch_a.FlowNodeIn.Id);
            Assert.AreEqual("branch_b", block_branch_b.FlowNodeIn.Id);
            Assert.AreSame(block_branch_a.FlowNodeIn, block_branch_a.NodeOut);
            Assert.AreSame(block_branch_b.FlowNodeIn, block_branch_b.NodeOut);

            // Vertex
            var actual_nodes_last = actual.GetNextNodesInFlowFrom(actual_nodeIn_nextNodes[0]);
            Assert.AreEqual(1, actual_nodes_last.Count);
            Assert.IsInstanceOfType(actual_nodes_last[0], typeof(BPMNFlowNode));
            Assert.AreEqual("end", actual_nodes_last[0].Id);
            Assert.AreSame(null, actual.NodeOut);
        }

        [TestMethod]
        public void CreateBlock()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("start").Flow("start", "elem_start")
                .Flow("elem_start", "branch_a")
                .Flow("elem_start", "branch_b")
                .Flow("branch_a", "elem_end")
                .Flow("branch_b", "elem_end"));

            var actual = (BPMNFlowNodesBlock)_sut.Transform(_bpmnProcess)
                .GetNextNodesInFlowFrom(_bpmnProcess.FlowNodeIn)[0];

            //
            // 1 Vertex + 2 Block + 1 Vertext
            //
            var vertices = actual.AllNodes;
            Assert.AreEqual(4, vertices.Count);
            // Vertex
            Assert.IsInstanceOfType(actual.FlowNodeIn, typeof(BPMNFlowNode));
            Assert.AreEqual("elem_start", actual.FlowNodeIn.Id);
            // Block
            var vertsFromStart = actual.GetNextNodesInFlowFrom(actual.FlowNodeIn);
            Assert.AreEqual(2, vertsFromStart.Count);

            var blockBranchA = (BPMNFlowNodesBlock)vertsFromStart[0];
            var blockBranchB = (BPMNFlowNodesBlock)vertsFromStart[1];
            Assert.AreEqual("block_branch_a", blockBranchA.Id);
            Assert.AreEqual("block_branch_b", blockBranchB.Id);
            Assert.AreEqual("branch_a", blockBranchA.FlowNodeIn.Id);
            Assert.AreEqual("branch_b", blockBranchB.FlowNodeIn.Id);
            Assert.AreSame(blockBranchA.FlowNodeIn, blockBranchA.NodeOut);
            Assert.AreSame(blockBranchB.FlowNodeIn, blockBranchB.NodeOut);

            // Vertex
            var vertsFormBlock = actual.GetNextNodesInFlowFrom(vertsFromStart[0]);
            Assert.AreEqual(1, vertsFormBlock.Count);
            Assert.IsInstanceOfType(vertsFormBlock[0], typeof(BPMNFlowNode));
            Assert.AreEqual("elem_end", vertsFormBlock[0].Id);
            Assert.AreEqual(null, actual.NodeOut);
        }

        [TestMethod]
        public void CreateBlock_inco_longer_sequential_flow_diverging_converging_longer_sequential_flow_diverging_converging_longer_sequential_flow()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("seq1_1")
                .Flow("seq1_1", "seq1_2")
                .Flow("seq1_2", "seq1_3")
                .Flow("seq1_3", "div1")
                .Flow("div1", "div1_branch_a")
                .Flow("div1", "div1_branch_b")
                .Flow("div1_branch_a", "conv1")
                .Flow("div1_branch_b", "conv1")
                .Flow("conv1", "seq2_1")
                .Flow("seq2_1", "seq2_2")
                .Flow("seq2_2", "seq2_3")
                .Flow("seq2_3", "div2")
                .Flow("div2", "div2_branch_a")
                .Flow("div2", "div2_branch_b")
                .Flow("div2_branch_a", "conv2")
                .Flow("div2_branch_b", "conv2")
                .Flow("conv2", "seq3_1")
                .Flow("seq3_1", "seq3_2")
                .Flow("seq3_2", "seq3_3"));
            var actual = _sut.Transform(_bpmnProcess);

            var seq1_1 = actual.FlowNodeIn;
            var seq1_2 = actual.GetNextNodesInFlowFrom(seq1_1)[0];
            var seq1_3 = actual.GetNextNodesInFlowFrom(seq1_2)[0];
            var block_div1 = actual.GetNextNodesInFlowFrom(seq1_3)[0];
            var seq2_1 = actual.GetNextNodesInFlowFrom(block_div1)[0];
            var seq2_2 = actual.GetNextNodesInFlowFrom(seq2_1)[0];
            var seq2_3 = actual.GetNextNodesInFlowFrom(seq2_2)[0];
            var block_div2 = actual.GetNextNodesInFlowFrom(seq2_3)[0];
            var seq3_1 = actual.GetNextNodesInFlowFrom(block_div2)[0];
            var seq3_2 = actual.GetNextNodesInFlowFrom(seq3_1)[0];
            var seq3_3 = actual.GetNextNodesInFlowFrom(seq3_2)[0];

            Verify(actual, "seq1_1", seq1_1);
            Verify(actual, "seq1_2", seq1_2);
            Verify(actual, "seq1_3", seq1_3);
            VerifyBlock(actual, "block_div1", block_div1);
            Verify(actual, "seq2_1", seq2_1);
            Verify(actual, "seq2_2", seq2_2);
            Verify(actual, "seq2_3", seq2_3);
            VerifyBlock(actual, "block_div2", block_div2);
            Verify(actual, "seq3_1", seq3_1);
            Verify(actual, "seq3_2", seq3_2);
            Verify(actual, "seq3_3", seq3_3);

            var block_div1_block = (BPMNFlowNodesBlock) block_div1;
            Verify(actual, "div1", block_div1_block.FlowNodeIn);
            VerifyBlock(actual, "block_div1_branch_a", block_div1_block.GetNextNodesInFlowFrom(block_div1_block.FlowNodeIn)[0]);
            VerifyBlock(actual, "block_div1_branch_b", block_div1_block.GetNextNodesInFlowFrom(block_div1_block.FlowNodeIn)[1]);
            Verify(actual, "conv1",
                   block_div1_block.GetNextNodesInFlowFrom(
                       block_div1_block.GetNextNodesInFlowFrom(block_div1_block.FlowNodeIn)[0])[0]);
            Verify(actual, "conv1",
                   block_div1_block.GetNextNodesInFlowFrom(
                        block_div1_block.GetNextNodesInFlowFrom(block_div1_block.FlowNodeIn)[1])[0]);
            Verify(actual, "conv1", block_div1_block.NodeOut);
        }

        [TestMethod]
        public void CreateBlock_inco_diverging_but_not_all_branches_converging_again()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("start")
                .Flow("start", "div1")
                .Flow("div1", "div1_branch_a")
                .Flow("div1", "div1_branch_b")
                .Flow("div1", "div1_branch_c")
                .Flow("div1", "div1_branch_d")
                .Flow("div1_branch_a", "conv1")
                .Flow("div1_branch_b", "conv1"));
            var actual = (BPMNFlowNodesBlock)_sut.Transform(_bpmnProcess).GetNextNodesInFlowFrom(_bpmnProcess.FlowNodeIn)[0];

            var div1 = actual.FlowNodeIn;
            var div1_branch_a = actual.GetNextNodesInFlowFrom(div1).First(x => x.Id == "block_div1_branch_a");
            var div1_branch_b = actual.GetNextNodesInFlowFrom(div1).First(x => x.Id == "block_div1_branch_b");
            var div1_branch_c = actual.GetNextNodesInFlowFrom(div1).First(x => x.Id == "block_div1_branch_c");
            var div1_branch_d = actual.GetNextNodesInFlowFrom(div1).First(x => x.Id == "block_div1_branch_d");

            var conv1_1 = actual.GetNextNodesInFlowFrom(div1_branch_a)[0];
            var conv1_2 = actual.GetNextNodesInFlowFrom(div1_branch_b)[0];

            Verify(actual, "div1", div1);
            VerifyBlock(actual, "block_div1_branch_a", div1_branch_a);
            VerifyBlock(actual, "block_div1_branch_b", div1_branch_b);
            VerifyBlock(actual, "block_div1_branch_c", div1_branch_c);
            VerifyBlock(actual, "block_div1_branch_d", div1_branch_d);

            Verify(actual, "conv1", conv1_1);
            Verify(actual, "conv1", conv1_2);

            Assert.AreEqual(0, actual.GetNextNodesInFlowFrom(div1_branch_c).Count);
            Assert.AreEqual(0, actual.GetNextNodesInFlowFrom(div1_branch_d).Count);

            Assert.AreEqual(actual.GetBPMNFlowNodeById("div1_branch_a"), ((BPMNFlowNodesBlock)div1_branch_a).NodeOut);
            Assert.AreEqual(actual.GetBPMNFlowNodeById("div1_branch_b"), ((BPMNFlowNodesBlock)div1_branch_b).NodeOut);
            Assert.AreEqual(null, ((BPMNFlowNodesBlock)div1_branch_d).NodeOut);
            Assert.AreEqual(null, ((BPMNFlowNodesBlock)div1_branch_c).NodeOut);
            Assert.AreEqual(null, ((BPMNFlowNodesBlock)div1_branch_d).NodeOut);
        }

        [TestMethod]
        [Ignore]
        public void CreateBlock_inco_TStyle_01()
        {
            CreateBPMNProcess(new tProcessCtor()
                .WithStartEvent("s")
                .Flow("s", "g1")
                .Branch("g1", "u1")
                .Branch("g1", "g2")
                .Flow("u1", "g2"));

            var actual = _sut.Transform(_bpmnProcess);

            var s = actual.FlowNodeIn;
            var sNexts = actual.GetNextNodesInFlowFrom(s);

            Assert.AreEqual(2, actual.AllNodes.Count);

            Verify(actual, "s", s);
            Assert.AreEqual(1, sNexts.Count);
            VerifyBlock(actual, "block_g1", sNexts[0]);

            var block_g1 = (BPMNFlowNodesBlock) sNexts[0];
            Verify(block_g1, "g1", block_g1.FlowNodeIn);
            Assert.AreEqual(3, block_g1.AllNodes.Count());

            var block_u1 = (BPMNFlowNodesBlock)block_g1.AllNodes.First(x => x.Id == "block_u1");
            var g2 = block_g1.AllNodes.First(x => x.Id == "g2");
            Assert.AreSame(actual.GetBPMNFlowNodeById("u1"), block_u1.FlowNodeIn);
            Assert.AreSame(actual.GetBPMNFlowNodeById("u1"), block_u1.NodeOut);
            Assert.AreSame(actual.GetBPMNFlowNodeById("g2"), block_g1.GetNextNodesInFlowFrom(block_u1)[0]);
            Assert.AreSame(g2, block_g1.GetNextNodesInFlowFrom(block_g1.FlowNodeIn)[1]);
        }

    }
}